﻿using System;
using System.Collections.Specialized;
using System.Configuration;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Web;
using System.Web.Caching;
using System.Web.Configuration;

namespace GateKeeper
{
    public class Settings
    {
        #region CORE
        private string configFileName = string.Empty;
        private string dataStoreMappedPath = HttpContext.Current.Server.MapPath("~/app_data/");
        #endregion // Core

        #region Constructor
        public Settings()
        {
            // Build config file dynamically from the assembly name
            configFileName = string.Format("{0}.config", GetType().Assembly.GetName().Name.ToLower());
        }
        #endregion // Constructor

        #region Private Declatations
        #region General
        private Boolean enablegatekeeper = true;
        private string accessdeniedmessage = string.Empty;
        private Boolean denyemptyuseragent = false;
        private Boolean displaycontactform = true;
        private string contactformurl = string.Empty;
        #endregion // General

        #region hotlinking
        private Boolean blockhotlinking = true;
        private string hotlinkexpression = string.Empty;
        private Boolean displayhotlinkdenyimage = false;
        private string hotlinkdenyimage = string.Empty;
        private string hotlinkallowedsites = string.Empty;
        #endregion // hotlinking

        #region honeypot
        private Boolean enablehoneypot = false;
        private string honeypotpath = string.Empty; // Virtual url path for the honeypot ex. honey/gottcha.aspx
        private Boolean enablehoneypotlogging = true; // Record each violation into the honeypot log
        private Boolean persisthoneypotdeny = false; // insert violators into IPAddress blacklist
        private Boolean enablehoneypotstats = false; // Listen for stats page request
        private string honeypotstatspath = string.Empty; // url to dynmically display stats ex. gatekeeper/stats.aspx
        private Boolean notifyadmin = false; // Send an email to the admin when the trap has been sprung
        #endregion // honeypot

        #region HttpBL
        private Boolean enablehttpbl = false;
        private Boolean enablehttpbllogging = false;
        private Boolean denyhttpblsuspect = false;
        private Boolean httpblpostonly = false;
        private string httpblkeycode = string.Empty;
        private string httpbltestipaddress = string.Empty;
        private int httpbltimeout = 3000;
        private int threatscorethreshold = 254;
        #endregion // HttpBL

        #region ProxyBL
        private Boolean enableproxybl = false;
        private Boolean enableproxybllogging = false;
        private Boolean denyproxyblsuspect = false;
        private Boolean proxyblpostonly = false;
        private string proxybltestipaddress = string.Empty;
        private int proxybltimeout = 3000;
        #endregion // ProxyBL

        #region DroneBL
        private Boolean enabledronebl = false;
        private Boolean enabledronebllogging = false;
        private Boolean denydroneblsuspect = false;
        private Boolean droneblpostonly = false;
        private string dronebltestipaddress = string.Empty;
        private int dronebltimeout = 3000;
        #endregion // DroneBL

        #region mail
        private string smtpemailaddress = string.Empty;
        private string smtpmessagesubject = string.Empty;
        private string smtpmessagebody = string.Empty;
        private string smtpservername = string.Empty;
        private string smtpusername = string.Empty;
        private string smtppassword = string.Empty;
        private Boolean storepasswordencrypted = false;
        private int smtpserverport = 25;
        private Boolean smtpenablessl = false;
        #endregion //mail

        #endregion // Private Declatations

        #region Public Declarations
        #region General
        public bool EnableGateKeeper
        {
            get { return enablegatekeeper; }
            set { enablegatekeeper = value; }
        }
        public string AccessDeniedMessage
        {
            get { return accessdeniedmessage; }
            set { accessdeniedmessage = value ?? string.Empty;}
        }
        public bool DenyEmptyUserAgent
        {
            get { return denyemptyuseragent; }
            set { denyemptyuseragent = value; }
        }
        public bool DisplayContactForm
        {
            get { return displaycontactform; }
            set { displaycontactform = value; }
        }
        public string ContactFormUrl
        {
            get { return contactformurl; }
            set { contactformurl = value ?? string.Empty; }
        }
        #endregion //General

        #region Hotlinking
        public bool BlockHotLinking
        {
            get { return blockhotlinking; }
            set { blockhotlinking = value; }
        }
        public bool DisplayHotLinkDenyImage
        {
            get { return displayhotlinkdenyimage; }
            set { displayhotlinkdenyimage = value; }
        }
        public string HotLinkExpression
        {
            get { return hotlinkexpression; }
            set { hotlinkexpression = value ?? string.Empty; }
        }
        public string HotLinkDenyImage
        {
            get { return hotlinkdenyimage; }
            set { hotlinkdenyimage = value ?? string.Empty; }
        }
        public string HotLinkAllowedSites
        {
            get { return hotlinkallowedsites; }
            set { hotlinkallowedsites = value ?? string.Empty; }
        }
        #endregion // Hotlinking

        #region Honeypot
        public bool EnableHoneyPot
        {
            get { return enablehoneypot; }
            set { enablehoneypot = value; }
        }
        public bool EnableHoneyPotLogging
        {
            get { return enablehoneypotlogging; }
            set { enablehoneypotlogging = value; }
        }
        public bool PersistHoneyPotDeny
        {
            get { return persisthoneypotdeny; }
            set { persisthoneypotdeny = value; }
        }
        public bool EnableHoneyPotStats
        {
            get { return enablehoneypotstats; }
            set { enablehoneypotstats = value; }
        }
        public bool NotifyAdmin
        {
            get { return notifyadmin; }
            set { notifyadmin = value; }
        }
        public string HoneyPotPath
        {
            get { return honeypotpath; }
            set { honeypotpath = value ?? string.Empty; }
        }
        public string HoneyPotStatsPath
        {
            get { return honeypotstatspath; }
            set { honeypotstatspath = value ?? string.Empty; }
        }

        #endregion // Honeypot

        #region HttpBL
        public Boolean EnableHttpBL
        {
            get { return enablehttpbl; }
            set { enablehttpbl = value; }
        }
        public Boolean EnableHttpBLLogging
        {
            get { return enablehttpbllogging; }
            set { enablehttpbllogging = value; }
        }
        public Boolean DenyHttpBLSuspect
        {
            get { return denyhttpblsuspect; }
            set { denyhttpblsuspect = value; }
        }
        public Boolean HttpBLPostOnly
        {
            get { return httpblpostonly; }
            set { httpblpostonly = value; }
        }
        public string HttpBLKeyCode
        {
            get { return httpblkeycode; }
            set { httpblkeycode = value; }
        }
        public string HttpBLTestIPAddress
        {
            get { return httpbltestipaddress; }
            set { httpbltestipaddress = value; }
        }
        public int HttpBLTimeout
        {
            get { return httpbltimeout; }
            set { httpbltimeout = value; }
        }
        public int ThreatScoreThreshold
        {
            get { return threatscorethreshold; }
            set { threatscorethreshold = value; }
        }
        #endregion // HttpBL

        #region ProxyBL
        public Boolean EnableProxyBL
        {
            get { return enableproxybl; }
            set { enableproxybl = value; }
        }
        public Boolean EnableProxyBLLogging
        {
            get { return enableproxybllogging; }
            set { enableproxybllogging = value; }
        }
        public Boolean DenyProxyBLSuspect
        {
            get { return denyproxyblsuspect; }
            set { denyproxyblsuspect = value; }
        }
        public Boolean ProxyBLPostOnly
        {
            get { return proxyblpostonly; }
            set { proxyblpostonly = value; }
        }
        public string ProxyBLTestIPAddress
        {
            get { return proxybltestipaddress; }
            set { proxybltestipaddress = value; }
        }
        public int ProxyBLTimeout
        {
            get { return proxybltimeout; }
            set { proxybltimeout = value; }
        }
        #endregion // ProxyBL

        #region DroneBL
        public Boolean EnableDroneBL
        {
            get { return enabledronebl; }
            set { enabledronebl = value; }
        }
        public Boolean EnableDroneBLLogging
        {
            get { return enabledronebllogging; }
            set { enabledronebllogging = value; }
        }
        public Boolean DenyDroneBLSuspect
        {
            get { return denydroneblsuspect; }
            set { denydroneblsuspect = value; }
        }
        public Boolean DroneBLPostOnly
        {
            get { return droneblpostonly; }
            set { droneblpostonly = value; }
        }
        public string DroneBLTestIPAddress
        {
            get { return dronebltestipaddress; }
            set { dronebltestipaddress = value; }
        }
        public int DroneBLTimeout
        {
            get { return dronebltimeout; }
            set { dronebltimeout = value; }
        }
        #endregion // DroneBL

        #region Mail
        public string SmtpEmailAddress
        {
            get { return smtpemailaddress; }
            set { smtpemailaddress = value ?? string.Empty; }
        }
        public string SmtpMessageSubject
        {
            get { return smtpmessagesubject; }
            set { smtpmessagesubject = value ?? string.Empty; }
        }
        public string SmtpMessageBody
        {
            get { return smtpmessagebody; }
            set { smtpmessagebody = value ?? string.Empty; }
        }
        public string SmtpServerName
        {
            get { return smtpservername; }
            set { smtpservername = value ?? string.Empty; }
        }
        public string SmtpUserName
        {
            get { return smtpusername; }
            set { smtpusername = value ?? string.Empty; }
        }
        public string SmtpPassword
        {
            get { return smtppassword; }
            set { smtppassword = value ?? string.Empty; }
        }
        public bool StorePasswordEncrypted
        {
            get { return storepasswordencrypted; }
            set { storepasswordencrypted = value; }
        }
        public bool SmtpEnableSSL
        {
            get { return smtpenablessl; }
            set { smtpenablessl = value; }
        }
        public int SmtpServerPort
        {
            get { return smtpserverport; }
            set { smtpserverport = value; }
        }
        #endregion // Mail

        #endregion // Public Declarations

        #region Public Methods
        public void Save()
        {
            StringDictionary dic = new StringDictionary();
            Type settingsType = this.GetType();
            foreach (PropertyInfo propertyInformation in settingsType.GetProperties())
            {
                try
                {
                    if (propertyInformation.Name != "Instance")
                    {
                        object propertyValue = propertyInformation.GetValue(this, null);
                        string valueAsString = propertyValue.ToString();

                        if (propertyValue.Equals(null) |
                            propertyValue.Equals(Int32.MinValue) |
                                propertyValue.Equals(Single.MinValue))
                        { valueAsString = String.Empty; }

                        dic.Add(propertyInformation.Name, valueAsString);
                    }
                }
                catch (Exception ex)
                {
                    GateKeeperModule.log.WarnFormat("Settings | Save : [{0}]", ex.Message);
                    GateKeeperModule.log.Error(ex.StackTrace);
                    throw ex;
                }
            }

            SaveSettings(dic);
            OnChanged();
        }
        public void Load()
        {
            // Generate cacheKey from Assembly Name
            string cacheKey = string.Format("{0}.cache", GetType().Assembly.GetName().Name.ToLower());

            // Check if the dictionary is already in cache
            StringDictionary dic = null;
            if (HttpRuntime.Cache.Get(cacheKey) != null)
            {
                // Load from cache
                dic = (StringDictionary)HttpRuntime.Cache.Get(cacheKey);
            }
            else
            {
                // Load from file
                dic = LoadSettings();

                // Load into cache for future use
                HttpRuntime.Cache.Insert(cacheKey, dic, new CacheDependency(dataStoreMappedPath + configFileName));
            }

            SetPropertyValues(dic);
        }
        #endregion // Public Methods

        #region Private Methods
        private void SetPropertyValues(StringDictionary dic)
        {
            GateKeeperModule.log.Debug("Entering SetPropertyValues");
            Type settingsType = this.GetType();
            foreach (string key in dic.Keys)
            {
                foreach (PropertyInfo propertyInformation in settingsType.GetProperties())
                {
                    if (propertyInformation.Name.Equals(key, StringComparison.OrdinalIgnoreCase))
                    {
                        try
                        {
                            propertyInformation.SetValue(this, Convert.ChangeType(dic[key], propertyInformation.PropertyType, CultureInfo.CurrentCulture), null);
                        }
                        catch (Exception ex)
                        {
                            GateKeeperModule.log.WarnFormat("SetPropertyValues : [{0}]", ex.Message);
                            GateKeeperModule.log.Error(ex.StackTrace);
                            throw ex;
                        }

                        break;
                    }
                }
            }
        }
        private StringDictionary LoadSettings()
        {
            GateKeeperModule.log.Debug("Loading configuration data");
            System.Configuration.Configuration config = GetConfig();
            GateKeeperModule.log.Debug("Config : Loaded");

            StringDictionary dic = new StringDictionary();
            foreach (string key in config.AppSettings.Settings.AllKeys)
            {
                GateKeeperModule.log.DebugFormat("    {0} = {1}", key, config.AppSettings.Settings[key].Value);
                dic.Add(key, config.AppSettings.Settings[key].Value);
            }

            GateKeeperModule.log.Debug("Configuration data loaded");
            return dic;
        }
        private Configuration GetConfig()
        {
            Configuration config = null;
            try
            {
                GateKeeperModule.log.DebugFormat("Locating config file : [{0}]", dataStoreMappedPath + configFileName);
                if (!File.Exists(dataStoreMappedPath + configFileName))
                {
                    GateKeeperModule.log.Debug("Config file not found");
                    config = NewConfigFile.Create(configFileName);
                    return config;
                }
                
                string physicalPath = HttpContext.Current.Server.MapPath(HttpContext.Current.Request.ApplicationPath);
                GateKeeperModule.log.DebugFormat("physicalPath : [{0}]", physicalPath);

                VirtualDirectoryMapping vDirMap = new VirtualDirectoryMapping(physicalPath, true, configFileName);
                WebConfigurationFileMap fileMap = new WebConfigurationFileMap();
                fileMap.VirtualDirectories.Add("/", vDirMap);
                return WebConfigurationManager.OpenMappedWebConfiguration(fileMap, "/app_data", "GateKeeper");
            }
            catch (Exception ex)
            {
                GateKeeperModule.log.WarnFormat("Settings GetConfig : [{0}]", ex.Message);
                GateKeeperModule.log.Error(ex.StackTrace);
                throw ex;
            }
        }
        private void SaveSettings(StringDictionary settings)
        {
            GateKeeperModule.log.Debug("Saving configuration data");
            try
            {
                Configuration config = GetConfig();
                GateKeeperModule.log.Debug("config : Loaded");
                foreach (string key in settings.Keys)
                {
                    GateKeeperModule.log.DebugFormat("    {0} = {1}", key, settings[key]);
                    config.AppSettings.Settings[key].Value = settings[key]; 
                }

                config.Save(ConfigurationSaveMode.Modified);
                GateKeeperModule.log.Debug("Configuration data saved");
            }
            catch (Exception ex)
            {
                GateKeeperModule.log.WarnFormat("SaveSettings : {0}", ex.Message);
                GateKeeperModule.log.Error(ex.StackTrace);
                throw ex;
            }
        }
        #endregion // Private Methods

        #region EventHandlers
        /// <summary>
        /// Occurs when the settings have been changed.
        /// </summary>
        public static event EventHandler<EventArgs> Changed;
        private static void OnChanged()
        {
            try 
            { 
                if (Settings.Changed != null) 
                {
                    GateKeeperModule.log.Debug("Executing Settings Changed eventHandler");
                    Settings.Changed(null, new EventArgs()); 
                } 
            }
            catch (Exception ex)
            {
                GateKeeperModule.log.WarnFormat("OnChanged : {0}", ex.Message);
                GateKeeperModule.log.Error(ex.StackTrace);
                throw ex;
            }
        }
        /// <summary>
        /// Occurs when the settings have been changed.
        /// </summary>
        public static event EventHandler<EventArgs> Changing;
        private static void OnChanging()
        {
            try 
            { 
                if (Settings.Changing != null)
                {
                    GateKeeperModule.log.Debug("Executing Settings Changing eventHandler");
                    Settings.Changing(null, new EventArgs()); 
                } 
            }
            catch (Exception ex)
            {
                GateKeeperModule.log.WarnFormat("OnChanging : {0}", ex.Message);
                GateKeeperModule.log.Error(ex.StackTrace);
                throw ex;
            }
        }
        #endregion // EventHandlers
    }
}
